﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using StyleMVVM.Validation.Impl.Fluent.Operators;

#if NETFX_CORE
using Windows.ApplicationModel.Resources;

#endif

namespace StyleMVVM.Validation.Impl.Fluent
{
	public class FluentRuleBuilder<T>
	{
		protected enum CurrentBlock
		{
			First,
			Second,
			Third
		}

		protected enum BlockLogicType
		{
			If,
			IfThen,
			IfThenElse,
			When,
			Unless
		}

		private static MethodInfo addErrorMethod;
		private static MethodInfo addRequiredMethod;
		private static MethodInfo addWarningMethod;
		private static PropertyInfo validationObjectProperty;
		private CurrentBlock currentBlock = CurrentBlock.First;
		private BlockLogicType? blockLogic;
		private IFluentValueProvider<T> leftValue;
		private IFluentOperator<T> currentOperator;
		private List<IFluentOperator<T>> firstBlock = new List<IFluentOperator<T>>();
		private List<IFluentOperator<T>> secondBlock;
		private List<IFluentOperator<T>> thirdBlock;
		private bool? firstAnd;
		private bool? secondAnd;
		private bool? thirdAnd;

		static FluentRuleBuilder()
		{
			addErrorMethod = typeof(IRuleExecutionContext).GetTypeInfo().GetDeclaredMethod("AddError");

			addRequiredMethod = typeof(IRuleExecutionContext).GetTypeInfo().GetDeclaredMethod("AddRequired");

			addWarningMethod = typeof(IRuleExecutionContext).GetTypeInfo().GetDeclaredMethod("AddWarning");

			validationObjectProperty = typeof(IRuleExecutionContext).GetTypeInfo().GetDeclaredProperty("ValidationObject");
		}

		public void SetValueProvider(IFluentValueProvider<T> valueProvider)
		{
			if (leftValue == null)
			{
				leftValue = valueProvider;
			}
			else if (currentOperator != null)
			{
				IFluentBinaryOperator<T> binaryOperator = currentOperator as IFluentBinaryOperator<T>;

				if (binaryOperator != null)
				{
					if (binaryOperator.RightValue == null)
					{
						binaryOperator.RightValue = valueProvider;
					}
					else
					{
						throw new SyntaxException(
							"You have already provided two values to this statement. You must use And/Or to continue");
					}
				}
				else
				{
					throw new SyntaxException(
						"The operator you have chosen does not allow for two values to be provided. You must use And/Or to continue");
				}
			}
			else
			{
				throw new SyntaxException("You must use an operator in between values");
			}
		}

		public void SetOperator(IFluentOperator<T> oFluentOperator)
		{
			if (oFluentOperator is IFluentLeftValueAware<T>)
			{
				if (leftValue != null)
				{
					currentOperator = oFluentOperator as IFluentLeftValueAware<T>;
					((IFluentLeftValueAware<T>)oFluentOperator).LeftValue = leftValue;
				}
				else
				{
					throw new SyntaxException("You must provide a value before using the operator");
				}
			}
			else
			{
				if (leftValue == null)
				{
					currentOperator = oFluentOperator;
				}
				else
				{
					throw new SyntaxException("You can not provide a value before using this operator");
				}
			}
		}

		public void SetAnd()
		{
			if (currentOperator == null)
			{
				currentOperator = new IsTrueOperator<T> { LeftValue = leftValue };
			}

			switch (currentBlock)
			{
				case CurrentBlock.First:
					if (firstAnd.HasValue && !firstAnd.Value)
					{
						throw new SyntaxException("You can not mix And/Or statements in the same logic block");
					}

					firstAnd = true;
					firstBlock.Add(currentOperator);
					break;

				case CurrentBlock.Second:
					if (secondAnd.HasValue && !secondAnd.Value)
					{
						throw new SyntaxException("You can not mix And/Or statements in the same logic block");
					}

					secondAnd = true;
					secondBlock.Add(currentOperator);
					break;

				case CurrentBlock.Third:
					if (thirdAnd.HasValue && !thirdAnd.Value)
					{
						throw new SyntaxException("You can not mix And/Or statements in the same logic block");
					}

					thirdAnd = true;
					thirdBlock.Add(currentOperator);
					break;
			}

			currentOperator = null;
			leftValue = null;
		}

		public void SetOr()
		{
			if (currentOperator == null)
			{
				if (leftValue == null)
				{
					throw new SyntaxException("You must provide a left side value to be tested for true");
				}

				currentOperator = new IsTrueOperator<T> { LeftValue = leftValue };
			}

			switch (currentBlock)
			{
				case CurrentBlock.First:
					if (firstAnd.HasValue && !firstAnd.Value)
					{
						throw new SyntaxException("You can not mix And/Or statements in the same logic block");
					}

					firstAnd = false;
					firstBlock.Add(currentOperator);
					break;

				case CurrentBlock.Second:
					if (secondAnd.HasValue && !secondAnd.Value)
					{
						throw new SyntaxException("You can not mix And/Or statements in the same logic block");
					}

					secondAnd = false;
					secondBlock.Add(currentOperator);
					break;

				case CurrentBlock.Third:
					if (thirdAnd.HasValue && !thirdAnd.Value)
					{
						throw new SyntaxException("You can not mix And/Or statements in the same logic block");
					}

					thirdAnd = false;
					thirdBlock.Add(currentOperator);
					break;
			}

			currentOperator = null;
			leftValue = null;
		}

		public void SetIf()
		{
			if (blockLogic.HasValue)
			{
				throw new SyntaxException(
					"You can't have multiple branches of logic. Please write a custom method to cover this case");
			}

			blockLogic = BlockLogicType.If;
		}

		public void SetThen()
		{
			if (!blockLogic.HasValue || blockLogic.Value != BlockLogicType.If)
			{
				throw new SyntaxException("You can only use a then statement after an If");
			}

			blockLogic = BlockLogicType.IfThen;
			currentBlock = CurrentBlock.Second;
			secondBlock = new List<IFluentOperator<T>>();

			if (currentOperator == null)
			{
				if (leftValue != null)
				{
					currentOperator = new IsTrueOperator<T> { LeftValue = leftValue };
				}
				else
				{
					throw new SyntaxException("You must have a statement before using When");
				}
			}

			firstBlock.Add(currentOperator);

			currentOperator = null;
			leftValue = null;
		}

		public void SetElse()
		{
			if (!blockLogic.HasValue || blockLogic.Value != BlockLogicType.IfThen)
			{
				throw new SyntaxException("You can only use an else statement after an If/Then");
			}

			blockLogic = BlockLogicType.IfThenElse;
			thirdBlock = new List<IFluentOperator<T>>();

			currentBlock = CurrentBlock.Third;

			if (currentOperator == null)
			{
				if (leftValue != null)
				{
					currentOperator = new IsTrueOperator<T> { LeftValue = leftValue };
				}
				else
				{
					throw new SyntaxException("You must have a statement before using When");
				}
			}

			secondBlock.Add(currentOperator);

			currentOperator = null;
			leftValue = null;
		}

		public void SetWhen()
		{
			if (blockLogic.HasValue)
			{
				throw new SyntaxException(
					"You can't have multiple branches of logic. Please write a custom method to cover this case");
			}

			blockLogic = BlockLogicType.When;
			currentBlock = CurrentBlock.Second;
			secondBlock = new List<IFluentOperator<T>>();

			if (currentOperator == null)
			{
				if (leftValue != null)
				{
					currentOperator = new IsTrueOperator<T> { LeftValue = leftValue };
				}
				else
				{
					throw new SyntaxException("You must have a statement before using When");
				}
			}

			firstBlock.Add(currentOperator);

			currentOperator = null;
			leftValue = null;
		}

		public void SetUnless()
		{
			if (blockLogic.HasValue)
			{
				throw new SyntaxException(
					"You can't have multiple branches of logic. Please write a custom method to cover this case");
			}

			blockLogic = BlockLogicType.Unless;
			currentBlock = CurrentBlock.Second;
			secondBlock = new List<IFluentOperator<T>>();

			if (currentOperator == null)
			{
				if (leftValue != null)
				{
					currentOperator = new IsTrueOperator<T> { LeftValue = leftValue };
				}
				else
				{
					throw new SyntaxException("You must have a statement before using When");
				}
			}

			firstBlock.Add(currentOperator);

			currentOperator = null;
			leftValue = null;
		}

		public Action<IRuleExecutionContext> Compile(ResourceLoader fluentResources)
		{
			Action<IRuleExecutionContext> returnValue = null;

			if (currentOperator == null)
			{
				if (leftValue == null && firstBlock.Count == 0)
				{
					throw new SyntaxException("Left value must be present to create a rule");
				}

				if (leftValue != null)
				{
					currentOperator = new IsTrueOperator<T> { LeftValue = leftValue };
				}
			}

			if (blockLogic.HasValue)
			{
				switch (blockLogic.Value)
				{
					case BlockLogicType.If:
						throw new SyntaxException("You must provide a then statement to go with your If");
						break;

					case BlockLogicType.IfThen:
						if (currentOperator != null)
						{
							secondBlock.Add(currentOperator);
						}

						returnValue = ProcessIfThen(fluentResources);
						break;

					case BlockLogicType.IfThenElse:
						if (currentOperator != null)
						{
							thirdBlock.Add(currentOperator);
						}

						returnValue = ProcessIfThenElse(fluentResources);
						break;

					case BlockLogicType.When:
						if (currentOperator != null)
						{
							secondBlock.Add(currentOperator);
						}

						returnValue = ProcessWhenRule(fluentResources);
						break;

					case BlockLogicType.Unless:
						if (currentOperator != null)
						{
							secondBlock.Add(currentOperator);
						}

						returnValue = ProcessUnlessRule(fluentResources);
						break;
				}
			}
			else
			{
				firstBlock.Add(currentOperator);

				returnValue = ProcessNoBlockLogic(fluentResources);
			}

			currentOperator = null;
			leftValue = null;

			return returnValue;
		}

		public void Clear()
		{
			leftValue = null;
			currentOperator = null;
			firstBlock = null;
			secondBlock = null;
			thirdBlock = null;
		}

		private Action<IRuleExecutionContext> ProcessNoBlockLogic(ResourceLoader fluentResources)
		{
			BlockExpression ruleBlock;
			ParameterExpression executionContext = Expression.Parameter(typeof(IRuleExecutionContext), "executionContext");
			ParameterExpression validationObject = Expression.Variable(typeof(T), "validationObject");

			FluentPropertyExpressionProvider<T> expressionProvider = new FluentPropertyExpressionProvider<T>();

			expressionProvider.ExecutionContext = executionContext;
			expressionProvider.ValidationObject = validationObject;

			string errorMessage = null;

			IEnumerable<Expression> testExpressions =
				CreateErrorCaseStatements(fluentResources, expressionProvider, firstAnd, firstBlock, out errorMessage);

			List<ParameterExpression> variables = new List<ParameterExpression> { validationObject };
			IEnumerable<ParameterExpression> outVariables;

			List<Expression> expressions =
				new List<Expression>(expressionProvider.PropertyExpressions(out outVariables));

			expressions.Insert(0,
			                   Expression.Assign(validationObject,
			                                     Expression.TypeAs(
				                                     Expression.Property(executionContext, validationObjectProperty),
				                                     typeof(T))));

			variables.AddRange(outVariables);

			expressions.AddRange(testExpressions);

			expressions.Add(
				Expression.Assign(Expression.Property(executionContext, "Message"),
				                  Expression.Constant(errorMessage, typeof(string))));

			ruleBlock = Expression.Block(variables, expressions.ToArray());

			return Expression.Lambda<Action<IRuleExecutionContext>>(ruleBlock, executionContext).Compile();
		}

		private Action<IRuleExecutionContext> ProcessIfThen(ResourceLoader fluentResources)
		{
			BlockExpression ruleBlock;
			ParameterExpression executionContext = Expression.Parameter(typeof(IRuleExecutionContext), "executionContext");
			ParameterExpression validationObject = Expression.Variable(typeof(T), "validationObject");

			FluentPropertyExpressionProvider<T> errorExpressionProvider = new FluentPropertyExpressionProvider<T>();

			errorExpressionProvider.ExecutionContext = executionContext;
			errorExpressionProvider.ValidationObject = validationObject;

			// This is first part of the error message before the when statement
			string errorString;

			// Create the error Expressions part of the statement (i.e. before when statement)
			IEnumerable<Expression> errorExpressions =
				CreateErrorCaseStatements(fluentResources, errorExpressionProvider, secondAnd, secondBlock, out errorString);

			// Combination of errorExpressions and the property statements from the errorExpressionProvider
			List<Expression> allErrorExpressions = new List<Expression>();
			IEnumerable<ParameterExpression> errorVariables;

			IEnumerable<Expression> propertyExpressions = errorExpressionProvider.PropertyExpressions(out errorVariables);

			allErrorExpressions.AddRange(propertyExpressions);
			allErrorExpressions.AddRange(errorExpressions);

			// Build the block Expression that represents all the statements before the When statement.
			Expression fullErrorExpression = Expression.Block(errorVariables, allErrorExpressions);

			FluentPropertyExpressionProvider<T> testExpressionProvider = new FluentPropertyExpressionProvider<T>();

			testExpressionProvider.ExecutionContext = executionContext;
			testExpressionProvider.ValidationObject = validationObject;

			// This will be the string that represents the if logic
			string logicString;

			// Create the Expression statement that represents the test condition in the If statement
			Expression testExpression =
				CreateTestStatements(fluentResources, testExpressionProvider, firstAnd, firstBlock, out logicString);

			// This is the list of statements that will be used to create the Main block for the Rule
			List<Expression> allStatements = new List<Expression>();
			IEnumerable<ParameterExpression> testVariables;

			// Add the assignment statement 
			//
			// T validationObject = executionContext.ValidationObject as T
			//
			allStatements.Add(Expression.Assign(validationObject,
			                                    Expression.TypeAs(
				                                    Expression.Property(executionContext, validationObjectProperty),
				                                    typeof(T))));

			// add all the statements for assigning properties
			// 
			// var property = validationObject.Property;
			// 
			allStatements.AddRange(
				testExpressionProvider.PropertyExpressions(out testVariables));

			// Create the main test statement for the Method
			//
			// if(testExpression)
			// {
			//    fullErrorExpression
			// }
			//
			allStatements.Add(Expression.IfThen(testExpression, fullErrorExpression));

			// Create the list of variables for the main block
			List<ParameterExpression> variables = new List<ParameterExpression> { validationObject };
			variables.AddRange(testVariables);

#if NETFX_CORE
			string ifThenMessage = fluentResources.GetString("IfThenMessage");
#else
			string ifThenMessage = FluentResources.IfThenMessage;
#endif

			// the WhenMessage is expected to be in if {0} then {1} format
			string errorMessage = string.Format(ifThenMessage, logicString, errorString);

			// Create assignment statement for message.
			allStatements.Add(
				Expression.Assign(Expression.Property(executionContext, "Message"),
				                  Expression.Constant(errorMessage, typeof(string))));

			ruleBlock = Expression.Block(variables, allStatements);

			return Expression.Lambda<Action<IRuleExecutionContext>>(ruleBlock, executionContext).Compile();
		}

		private Action<IRuleExecutionContext> ProcessIfThenElse(ResourceLoader fluentResources)
		{
			BlockExpression ruleBlock;
			ParameterExpression executionContext = Expression.Parameter(typeof(IRuleExecutionContext), "executionContext");
			ParameterExpression validationObject = Expression.Variable(typeof(T), "validationObject");

			FluentPropertyExpressionProvider<T> thenErrorExpressionProvider = new FluentPropertyExpressionProvider<T>();

			thenErrorExpressionProvider.ExecutionContext = executionContext;
			thenErrorExpressionProvider.ValidationObject = validationObject;

			// This is first part of the error message before the when statement
			string thenErrorString;

			// Create the error Expressions part of the statement (i.e. before when statement)
			IEnumerable<Expression> thenErrorExpressions =
				CreateErrorCaseStatements(fluentResources, thenErrorExpressionProvider, secondAnd, secondBlock, out thenErrorString);

			// Combination of errorExpressions and the property statements from the errorExpressionProvider
			List<Expression> allThenErrorExpressions = new List<Expression>();
			IEnumerable<ParameterExpression> thenErrorVariables;

			IEnumerable<Expression> thenPropertyExpressions =
				thenErrorExpressionProvider.PropertyExpressions(out thenErrorVariables);

			allThenErrorExpressions.AddRange(thenPropertyExpressions);
			allThenErrorExpressions.AddRange(thenErrorExpressions);

			// Build the block Expression that represents all the statements before the When statement.
			Expression fullThenErrorExpression = Expression.Block(thenErrorVariables, allThenErrorExpressions);

			FluentPropertyExpressionProvider<T> elseErrorExpressionProvider = new FluentPropertyExpressionProvider<T>();

			elseErrorExpressionProvider.ExecutionContext = executionContext;
			elseErrorExpressionProvider.ValidationObject = validationObject;

			// This is first part of the error message before the when statement
			string elseErrorString;

			// Create the error Expressions part of the statement (i.e. before when statement)
			IEnumerable<Expression> elseErrorExpressions =
				CreateErrorCaseStatements(fluentResources, elseErrorExpressionProvider, thirdAnd, thirdBlock, out elseErrorString);

			// Combination of errorExpressions and the property statements from the errorExpressionProvider
			List<Expression> allElseErrorExpressions = new List<Expression>();
			IEnumerable<ParameterExpression> elseErrorVariables;

			IEnumerable<Expression> elsePropertyExpressions =
				elseErrorExpressionProvider.PropertyExpressions(out elseErrorVariables);

			allElseErrorExpressions.AddRange(elsePropertyExpressions);
			allElseErrorExpressions.AddRange(elseErrorExpressions);

			// Build the block Expression that represents all the statements before the When statement.
			Expression fullElseErrorExpression = Expression.Block(elseErrorVariables, allElseErrorExpressions);

			FluentPropertyExpressionProvider<T> testExpressionProvider = new FluentPropertyExpressionProvider<T>();

			testExpressionProvider.ExecutionContext = executionContext;
			testExpressionProvider.ValidationObject = validationObject;

			// This will be the string that represents the if logic
			string logicString;

			// Create the Expression statement that represents the test condition in the If statement
			Expression testExpression =
				CreateTestStatements(fluentResources, testExpressionProvider, firstAnd, firstBlock, out logicString);

			// This is the list of statements that will be used to create the Main block for the Rule
			List<Expression> allStatements = new List<Expression>();
			IEnumerable<ParameterExpression> testVariables;

			// Add the assignment statement 
			//
			// T validationObject = executionContext.ValidationObject as T
			//
			allStatements.Add(Expression.Assign(validationObject,
			                                    Expression.TypeAs(
				                                    Expression.Property(executionContext, validationObjectProperty),
				                                    typeof(T))));

			// add all the statements for assigning properties
			// 
			// var property = validationObject.Property;
			// 
			allStatements.AddRange(
				testExpressionProvider.PropertyExpressions(out testVariables));

			// Create the main test statement for the Method
			//
			// if(testExpression)
			// {
			//    fullErrorExpression
			// }
			//
			allStatements.Add(Expression.IfThenElse(testExpression, fullThenErrorExpression, fullElseErrorExpression));

			// Create the list of variables for the main block
			List<ParameterExpression> variables = new List<ParameterExpression> { validationObject };
			variables.AddRange(testVariables);

#if NETFX_CORE
			string ifThenElseMessage = fluentResources.GetString("IfThenElseMessage");
#else

			string ifThenElseMessage = FluentResources.IfThenElseMessage;
#endif

			// the WhenMessage is expected to be in if {0} then {1} else {2} format
			string errorMessage = string.Format(ifThenElseMessage, logicString, thenErrorString, elseErrorString);

			// Create assignment statement for message.
			allStatements.Add(
				Expression.Assign(Expression.Property(executionContext, "Message"),
				                  Expression.Constant(errorMessage, typeof(string))));

			ruleBlock = Expression.Block(variables, allStatements);

			return Expression.Lambda<Action<IRuleExecutionContext>>(ruleBlock, executionContext).Compile();
		}

		private Action<IRuleExecutionContext> ProcessUnlessRule(ResourceLoader fluentResources)
		{
			BlockExpression ruleBlock;
			ParameterExpression executionContext = Expression.Parameter(typeof(IRuleExecutionContext), "executionContext");
			ParameterExpression validationObject = Expression.Variable(typeof(T), "validationObject");

			FluentPropertyExpressionProvider<T> errorExpressionProvider = new FluentPropertyExpressionProvider<T>();

			errorExpressionProvider.ExecutionContext = executionContext;
			errorExpressionProvider.ValidationObject = validationObject;

			// This is first part of the error message before the when statement
			string errorString;

			// Create the error Expressions part of the statement (i.e. before when statement)
			IEnumerable<Expression> errorExpressions =
				CreateErrorCaseStatements(fluentResources, errorExpressionProvider, firstAnd, firstBlock, out errorString);

			// Combination of errorExpressions and the property statements from the errorExpressionProvider
			List<Expression> allErrorExpressions = new List<Expression>();
			IEnumerable<ParameterExpression> errorVariables;

			IEnumerable<Expression> propertyExpressions = errorExpressionProvider.PropertyExpressions(out errorVariables);

			allErrorExpressions.AddRange(propertyExpressions);
			allErrorExpressions.AddRange(errorExpressions);

			// Build the block Expression that represents all the statements before the When statement.
			Expression fullErrorExpression = Expression.Block(errorVariables, allErrorExpressions);

			FluentPropertyExpressionProvider<T> testExpressionProvider = new FluentPropertyExpressionProvider<T>();

			testExpressionProvider.ExecutionContext = executionContext;
			testExpressionProvider.ValidationObject = validationObject;

			// This will be used in the error message after When
			string logicString;

			// Create the Expression statement that represents the part after the When
			Expression testExpression =
				CreateTestStatements(fluentResources, testExpressionProvider, secondAnd, secondBlock, out logicString);

			// Add a Not operator to testExpression to represent the Unless aspect
			testExpression = Expression.Not(testExpression);

			// This is the list of statements that will be used to create the Main block for the Rule
			List<Expression> allStatements = new List<Expression>();
			IEnumerable<ParameterExpression> testVariables;

			// Add the assignment statement 
			//
			// T validationObject = executionContext.ValidationObject as T
			//
			allStatements.Add(Expression.Assign(validationObject,
			                                    Expression.TypeAs(
				                                    Expression.Property(executionContext, validationObjectProperty),
				                                    typeof(T))));

			// add all the statements for assigning properties
			// 
			// var property = validationObject.Property;
			// 
			allStatements.AddRange(
				testExpressionProvider.PropertyExpressions(out testVariables));

			// Create the main test statement for the Method
			//
			// if(testExpression)
			// {
			//    fullErrorExpression
			// }
			//
			allStatements.Add(Expression.IfThen(testExpression, fullErrorExpression));

			// Create the list of variables for the main block
			List<ParameterExpression> variables = new List<ParameterExpression> { validationObject };
			variables.AddRange(testVariables);

#if NETFX_CORE
			string unlessMessage = fluentResources.GetString("UnlessMessage");
#else
			string unlessMessage = FluentResources.UnlessMessage;
#endif

			// the WhenMessage is expected to be in {0} when {1} format
			string errorMessage = string.Format(unlessMessage, errorString, logicString);

			// Create assignment statement for message.
			allStatements.Add(
				Expression.Assign(Expression.Property(executionContext, "Message"),
				                  Expression.Constant(errorMessage, typeof(string))));

			ruleBlock = Expression.Block(variables, allStatements);

			return Expression.Lambda<Action<IRuleExecutionContext>>(ruleBlock, executionContext).Compile();
		}

		private Action<IRuleExecutionContext> ProcessWhenRule(ResourceLoader fluentResources)
		{
			BlockExpression ruleBlock;
			ParameterExpression executionContext = Expression.Parameter(typeof(IRuleExecutionContext), "executionContext");
			ParameterExpression validationObject = Expression.Variable(typeof(T), "validationObject");

			FluentPropertyExpressionProvider<T> errorExpressionProvider = new FluentPropertyExpressionProvider<T>();

			errorExpressionProvider.ExecutionContext = executionContext;
			errorExpressionProvider.ValidationObject = validationObject;

			// This is first part of the error message before the when statement
			string errorString;

			// Create the error Expressions part of the statement (i.e. before when statement)
			IEnumerable<Expression> errorExpressions =
				CreateErrorCaseStatements(fluentResources, errorExpressionProvider, firstAnd, firstBlock, out errorString);

			// Combination of errorExpressions and the property statements from the errorExpressionProvider
			List<Expression> allErrorExpressions = new List<Expression>();
			IEnumerable<ParameterExpression> errorVariables;

			IEnumerable<Expression> propertyExpressions = errorExpressionProvider.PropertyExpressions(out errorVariables);

			allErrorExpressions.AddRange(propertyExpressions);
			allErrorExpressions.AddRange(errorExpressions);

			// Build the block Expression that represents all the statements before the When statement.
			Expression fullErrorExpression = Expression.Block(errorVariables, allErrorExpressions);

			FluentPropertyExpressionProvider<T> testExpressionProvider = new FluentPropertyExpressionProvider<T>();

			testExpressionProvider.ExecutionContext = executionContext;
			testExpressionProvider.ValidationObject = validationObject;

			// This will be used in the error message after When
			string logicString;

			// Create the Expression statement that represents the part after the When
			Expression testExpression =
				CreateTestStatements(fluentResources, testExpressionProvider, secondAnd, secondBlock, out logicString);

			// This is the list of statements that will be used to create the Main block for the Rule
			List<Expression> allStatements = new List<Expression>();
			IEnumerable<ParameterExpression> testVariables;

			// Add the assignment statement 
			//
			// T validationObject = executionContext.ValidationObject as T
			//
			allStatements.Add(Expression.Assign(validationObject,
			                                    Expression.TypeAs(
				                                    Expression.Property(executionContext, validationObjectProperty),
				                                    typeof(T))));

			// add all the statements for assigning properties
			// 
			// var property = validationObject.Property;
			// 
			allStatements.AddRange(
				testExpressionProvider.PropertyExpressions(out testVariables));

			// Create the main test statement for the Method
			//
			// if(testExpression)
			// {
			//    fullErrorExpression
			// }
			//
			allStatements.Add(Expression.IfThen(testExpression, fullErrorExpression));

			// Create the list of variables for the main block
			List<ParameterExpression> variables = new List<ParameterExpression> { validationObject };
			variables.AddRange(testVariables);

#if NETFX_CORE
			string whenMessage = fluentResources.GetString("WhenMessage");
#else
			string whenMessage = FluentResources.WhenMessage;
#endif

			// the WhenMessage is expected to be in {0} when {1} format
			string errorMessage = string.Format(whenMessage, errorString, logicString);

			// Create assignment statement for message.
			allStatements.Add(
				Expression.Assign(Expression.Property(executionContext, "Message"),
				                  Expression.Constant(errorMessage, typeof(string))));

			ruleBlock = Expression.Block(variables, allStatements);

			return Expression.Lambda<Action<IRuleExecutionContext>>(ruleBlock, executionContext).Compile();
		}

		/// <summary>
		/// Create the statement to be used in an If statement, it does not flag any errors
		/// </summary>
		/// <param name="fluentResources"></param>
		/// <param name="expressionProvider"></param>
		/// <param name="andOrLogic"></param>
		/// <param name="logicBlocks"></param>
		/// <param name="logicMessage"></param>
		/// <returns></returns>
		private Expression CreateTestStatements(ResourceLoader fluentResources,
		                                        FluentPropertyExpressionProvider<T> expressionProvider,
		                                        bool? andOrLogic,
		                                        List<IFluentOperator<T>> logicBlocks,
		                                        out string logicMessage)
		{
			logicMessage = null;

			if (andOrLogic.HasValue)
			{
				string logicString = null;
				List<Tuple<IFluentOperator<T>, Expression, string>> expresions =
					new List<Tuple<IFluentOperator<T>, Expression, string>>();

#if NETFX_CORE
				if (andOrLogic.Value)
				{
					logicString = fluentResources.GetString("And");
				}
				else
				{
					logicString = fluentResources.GetString("Or");
				}
#else
				if (andOrLogic.Value)
				{
					logicString = FluentResources.And;
				}
				else
				{
					logicString = FluentResources.Or;
				}
#endif

				foreach (IFluentOperator<T> fluentOperator in logicBlocks)
				{
					Expression testExpression = fluentOperator.GenerateExpression(expressionProvider);

#if NETFX_CORE
					string currentLogicMessage = fluentOperator.GenerateLogicStatement(fluentResources);
#else
					string currentLogicMessage = fluentOperator.GenerateLogicStatement();
#endif

					expresions.Add(new Tuple<IFluentOperator<T>, Expression, string>(fluentOperator,
					                                                                 testExpression,
					                                                                 currentLogicMessage));
				}

				Expression leftSide = null;
				List<Expression> propertyErrorStatements = new List<Expression>();

				foreach (Tuple<IFluentOperator<T>, Expression, string> expresion in expresions)
				{
					if (leftSide == null)
					{
						leftSide = expresion.Item2;
					}
					else
					{
						if (andOrLogic.Value)
						{
							leftSide = Expression.AndAlso(leftSide, expresion.Item2);
						}
						else
						{
							leftSide = Expression.OrElse(leftSide, expresion.Item2);
						}
					}

					if (!string.IsNullOrEmpty(logicMessage))
					{
						logicMessage += logicString + " ";
					}

					logicMessage += expresion.Item3;
				}

				return leftSide;
			}

			Expression returnExpression = logicBlocks[0].GenerateExpression(expressionProvider);

#if NETFX_CORE
			logicMessage = logicBlocks[0].GenerateLogicStatement(fluentResources);
#else
			logicMessage = logicBlocks[0].GenerateLogicStatement();
#endif

			return returnExpression;
		}

		/// <summary>
		/// Creates a set of statements to be used as part of the Then case or Else case of a rule.
		/// It will flag properties for errors 
		/// </summary>
		/// <param name="fluentResources"></param>
		/// <param name="expressionProvider"></param>
		/// <param name="andOrLogic"></param>
		/// <param name="logicBlocks"></param>
		/// <param name="errorMessage"></param>
		/// <returns></returns>
		private IEnumerable<Expression> CreateErrorCaseStatements(ResourceLoader fluentResources,
		                                                          FluentPropertyExpressionProvider<T> expressionProvider,
		                                                          bool? andOrLogic,
		                                                          List<IFluentOperator<T>> logicBlocks,
		                                                          out string errorMessage)
		{
			List<Expression> testExpressions = new List<Expression>();

			errorMessage = null;

			if (andOrLogic.HasValue)
			{
				if (andOrLogic.Value)
				{
#if NETFX_CORE
					string andString = fluentResources.GetString("And");
#else
					string andString = FluentResources.And;
#endif

					foreach (IFluentOperator<T> fluentOperator in logicBlocks)
					{
						Expression testExpression = fluentOperator.GenerateExpression(expressionProvider);

#if NETFX_CORE
						string currentErrorMessage = fluentOperator.GenerateErrorMessage(fluentResources);
#else
						string currentErrorMessage = fluentOperator.GenerateErrorMessage();
#endif

						Expression ifTest =
							Expression.IfThen(
								Expression.IsFalse(testExpression),
								Expression.Block(GeneratePropertyErrorMessage(fluentResources,
								                                              fluentOperator,
								                                              expressionProvider,
								                                              currentErrorMessage)));

						testExpressions.Add(ifTest);

						if (!string.IsNullOrEmpty(errorMessage))
						{
							errorMessage += andString + " ";
						}

						errorMessage += currentErrorMessage;
					}
				}
				else
				{
#if NETFX_CORE
					string orString = fluentResources.GetString("Or");
#else
					string orString = FluentResources.Or;
#endif

					List<Tuple<IFluentOperator<T>, Expression, string>> expresions =
						new List<Tuple<IFluentOperator<T>, Expression, string>>();

					foreach (IFluentOperator<T> fluentOperator in logicBlocks)
					{
						Expression testExpression = fluentOperator.GenerateExpression(expressionProvider);

#if NETFX_CORE
						string currentErrorMessage = fluentOperator.GenerateErrorMessage(fluentResources);
#else
						string currentErrorMessage = fluentOperator.GenerateErrorMessage();
#endif
						expresions.Add(new Tuple<IFluentOperator<T>, Expression, string>(fluentOperator,
						                                                                 testExpression,
						                                                                 currentErrorMessage));
					}

					Expression leftSide = null;
					List<Expression> propertyErrorStatements = new List<Expression>();

					foreach (Tuple<IFluentOperator<T>, Expression, string> expresion in expresions)
					{
						if (leftSide == null)
						{
							leftSide = expresion.Item2;
						}
						else
						{
							leftSide = Expression.OrElse(leftSide, expresion.Item2);
						}

						propertyErrorStatements.AddRange(
							GeneratePropertyErrorMessage(fluentResources,
							                             expresion.Item1,
							                             expressionProvider,
							                             expresion.Item3));

						if (!string.IsNullOrEmpty(errorMessage))
						{
							errorMessage += orString + " ";
						}

						errorMessage += expresion.Item3;
					}

					Expression ifExpression =
						Expression.IfThen(Expression.Not(leftSide), Expression.Block(propertyErrorStatements));

					testExpressions.Add(ifExpression);
				}
			}
			else
			{
				Expression testExpression = logicBlocks[0].GenerateExpression(expressionProvider);

#if NETFX_CORE
				errorMessage = logicBlocks[0].GenerateErrorMessage(fluentResources);
#else
				errorMessage = logicBlocks[0].GenerateErrorMessage();
#endif

				Expression ifTest =
					Expression.IfThen(
						Expression.IsFalse(testExpression),
						Expression.Block(GeneratePropertyErrorMessage(fluentResources, logicBlocks[0], expressionProvider, errorMessage)));

				testExpressions.Add(ifTest);
			}

			return testExpressions;
		}

		/// <summary>
		/// Create the statement for calling executionContext.AddError or executionContext.AddRequired
		/// </summary>
		/// <param name="fluentResources"></param>
		/// <param name="provider"></param>
		/// <param name="expressionProvider"></param>
		/// <param name="message"></param>
		/// <returns></returns>
		private IEnumerable<Expression> GeneratePropertyErrorMessage(ResourceLoader fluentResources,
		                                                             IFluentOperator<T> provider,
		                                                             FluentPropertyExpressionProvider<T> expressionProvider,
		                                                             string message)
		{
			IFluentLeftValueAware<T> leftValueAware = provider as IFluentLeftValueAware<T>;

			MethodInfo addMethod = addErrorMethod;

			if (provider.IsRequired)
			{
				addMethod = addRequiredMethod;
			}

			if (leftValueAware != null)
			{
				IFluentPropertyValueProvider<T> propertyValueProvider = leftValueAware.LeftValue as IFluentPropertyValueProvider<T>;

				if (propertyValueProvider != null)
				{
					yield return Expression.Call(expressionProvider.ExecutionContext,
					                             addMethod,
					                             Expression.Constant(propertyValueProvider.PropertyName, typeof(string)),
					                             Expression.Constant(message, typeof(string)));
				}
			}

			IFluentBinaryOperator<T> binaryOperator = provider as IFluentBinaryOperator<T>;

			if (binaryOperator != null)
			{
				IFluentPropertyValueProvider<T> valueProvider = binaryOperator.RightValue as IFluentPropertyValueProvider<T>;

				if (valueProvider != null)
				{
					yield return Expression.Call(expressionProvider.ExecutionContext,
					                             addMethod,
					                             Expression.Constant(valueProvider.PropertyName, typeof(string)),
					                             Expression.Constant(message, typeof(string)));
				}
			}
		}
	}
}